<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <meta charset="utf-8">
    <meta name="keywords" content="第7章 模型对象的序列化, 湮灭星空,博客,Python,后端">
    <meta name="description" content="最适合Python JSON序列化的是dict字典类型，每一种语言都有其对应的数据结构用来对应JSON对象，比如在PHP中是它的数组数据结构。而Python是用字典来对应JSON的。如果我们想直接序列化一个对象或者模型对象，那么最笨的办法是">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="renderer" content="webkit|ie-stand|ie-comp">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <title>第7章 模型对象的序列化 | 湮灭星空</title>
    <link rel="icon" type="image/png" href="/favicon.png">

    <link rel="stylesheet" type="text/css" href="/libs/awesome/css/all.css">
    <link rel="stylesheet" type="text/css" href="/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="/css/matery.css">
    <link rel="stylesheet" type="text/css" href="/css/my.css">

    <script src="/libs/jquery/jquery.min.js"></script>

<link rel="alternate" href="/atom.xml" title="湮灭星空" type="application/atom+xml">
</head>

<body>
    <header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper head-container">
            <div class="brand-logo">
                <a href="/" class="waves-effect waves-light">
                    
                    <img src="/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">湮灭星空</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/tags" class="waves-effect waves-light">
      
      <i class="fas fa-tags" style="zoom: 0.6;"></i>
      
      <span>标签</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/categories" class="waves-effect waves-light">
      
      <i class="fas fa-bookmark" style="zoom: 0.6;"></i>
      
      <span>分类</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/archives" class="waves-effect waves-light">
      
      <i class="fas fa-archive" style="zoom: 0.6;"></i>
      
      <span>归档</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/about" class="waves-effect waves-light">
      
      <i class="fas fa-user-circle" style="zoom: 0.6;"></i>
      
      <span>关于</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/contact" class="waves-effect waves-light">
      
      <i class="fas fa-comments" style="zoom: 0.6;"></i>
      
      <span>留言板</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/friends" class="waves-effect waves-light">
      
      <i class="fas fa-address-book" style="zoom: 0.6;"></i>
      
      <span>友链</span>
    </a>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
</ul>

<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
        <img src="/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">湮灭星空</div>
        <div class="logo-desc">
            
            天下寥寥，苍生涂涂，诸子百家，唯我纵横
            
        </div>
    </div>

    

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/tags" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-tags"></i>
			
			标签
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/categories" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-bookmark"></i>
			
			分类
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/archives" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-archive"></i>
			
			归档
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/about" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-user-circle"></i>
			
			关于
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/contact" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-comments"></i>
			
			留言板
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/friends" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-address-book"></i>
			
			友链
		</a>
          
        </li>
        
        
    </ul>
</div>

        </div>

        
    </nav>

</header>

    



<div class="bg-cover pd-header post-cover" style="background-image: url('/medias/featureimages/15.jpg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <div class="description center-align post-title">
                        第7章 模型对象的序列化
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        padding-left: 20px;
    }

    .toc-widget .toc-title {
        margin: 35px 0 15px 0;
        padding-left: 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #toc-content .is-active-link::before {
        background-color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                    <div class="article-tag">
                        
                            <a href="/tags/python/">
                                <span class="chip bg-color">python</span>
                            </a>
                        
                            <a href="/tags/flask/">
                                <span class="chip bg-color">flask</span>
                            </a>
                        
                            <a href="/tags/restful/">
                                <span class="chip bg-color">restful</span>
                            </a>
                        
                            <a href="/tags/api/">
                                <span class="chip bg-color">api</span>
                            </a>
                        
                    </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                    <div class="post-cate">
                        <i class="fas fa-bookmark fa-fw icon-category"></i>
                        
                            <a href="/categories/ginger/" class="post-category">
                                ginger
                            </a>
                        
                    </div>
                    
                </div>
            </div>

            <div class="post-info">
                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-minus fa-fw"></i>发布日期:&nbsp;&nbsp;
                    2019-04-12
                </div>
                

                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-check fa-fw"></i>更新日期:&nbsp;&nbsp;
                    2019-12-06
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-file-word fa-fw"></i>文章字数:&nbsp;&nbsp;
                    4k
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-clock fa-fw"></i>阅读时长:&nbsp;&nbsp;
                    14 分
                </div>
                
				
                
                    <div id="busuanzi_container_page_pv" class="info-break-policy">
                        <i class="far fa-eye fa-fw"></i>阅读次数:&nbsp;&nbsp;
                        <span id="busuanzi_value_page_pv"></span>
                    </div>
				
            </div>
            
        </div>
        <hr class="clearfix">
        <div class="card-content article-card-content">
            <div id="articleContent">
                <p>最适合Python JSON序列化的是dict字典类型，每一种语言都有其对应的数据结构用来对应JSON对象，比如在PHP中是它的数组数据结构。而Python是用字典来对应JSON的。如果我们想直接序列化一个对象或者模型对象，那么最笨的办法是把对象的属性读取出来，然后组装成一个字典再序列化。这实在是太麻烦了。本章节我们将深入了解JSO…</p>
<h3 id="7-1-鸡汤？"><a class="header-anchor" href="#7-1-鸡汤？"></a>7-1 鸡汤？</h3>
<p>在第6章我们查询到 User 这个模型对象之后，需要探讨的一个问题就是如何将 User 返回到客户端去。以前我们视图函数的返回结果要不就是字符串要不就是 APIException，这两种情况都很好解决，那么如果我们要返回一个模型对象的话，应该怎么办？</p>
<p>如果我们直接返回模型对象的，实际上就是我们在思维导图里面说的返回的是业务数据信息，它不是一个异常信息。直接 return user会报错，如下两张图。</p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051610.jpg" alt="image-20190103231720450"></p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051614.jpg" alt="image-20190103231846530"></p>
<p>在 Python 高级编程中曾经讲到过，在 Python 里最适合做序列化的是字典这种数据结构。如果我们要把 user 的相关信息返回去，我们可以尝试把 user 中的相关数据读取出来，拼接成字典，再把字典序列化返回到客户端去。</p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051617.jpg" alt="image-20190104105316303"></p>
<h3 id="7-2-理解序列化时的default函数"><a class="header-anchor" href="#7-2-理解序列化时的default函数"></a>7-2 理解序列化时的default函数</h3>
<p>上节创建字典序列化的方法比较繁琐，我们可不可以直接序列化 user 对象呢？答案是不可以。但是我们可以重写 jsonify 让其可以直接序列化对象，这就比较高级了。如何重写呢？</p>
<p>首先打开 jsonify 源码 anaconda3/envs/flask_restful_api/lib/python3.6/site-packages/flask/json/<strong>init</strong>.py，我们在下面两张图的位置打断点，可以明确知道<code>jsonify</code>的断点会进入到 <code>JSONEncoder.default</code>内部，参数<code>o</code>就是传给<code>jsonify</code>的对象，在该函数内部依次比对是否是 <code>datetime</code>、<code>date</code>、<code>uuid.UUID</code>、<code>__html__</code>，如果都不是的话，则调用<code>_json.JSONEncoder.default(self, o)</code>返回。</p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051619.jpg" alt="image-20190104110803018"></p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051620.jpg" alt="image-20190104110734852"></p>
<blockquote>
<p><code>_json.JSONEncoder.default(self, o)</code>函数</p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051623.jpg" alt="image-20190104111331719"></p>
</blockquote>
<p>那么问题来了：是不是只要调用 <code>jsonify</code>就一定会调用<code>JSONEncoder.default</code>呢？答案为否。</p>
<p>验证一下，这次<code>jsonify(r)</code>，事实证明断点并没有走进<code>JSONEncoder.default</code>函数内部。</p>
<p>那么什么时候才会调用<code>JSONEncoder.default</code>函数呢？</p>
<p>如果 flask 知道如何序列化传入的数据结构的时候，它是不会去调用<code>JSONEncoder.default</code>函数的。因为它知道怎么序列化，它就直接帮你序列化了。但是如果说我们要序列化的是一个对象，比如 user 模型。flask 默认是不知道怎么序列化模型的，那么 flask 就回去调用<code>JSONEncoder.default</code>函数。</p>
<p>那么 flask 为什么在不能序列化一个数据结构的时候会调用<code>JSONEncoder.default</code>函数呢？</p>
<p>原因在于 flask 不知道怎么序列化，但是它可以给我们一个途径，让我们指明这个数据结构应该如何序列化。换句话来说我们需要在<code>JSONEncoder.default</code>函数内部，把不能序列化的数据结构转换成能够序列化的数据结构。举个例子来讲，如果说<code>JSONEncoder.default</code>这里传进来的是一个 user 对象，user 对象是不能序列化的，如果说我们可以把 user 对象转换成字典，而字典是可以序列化的，那么这样就可以完成 user 对象的序列化了。虽然 user 对象是不能序列化的，但是我们可以将 user 对象的信息读取出来转换成字典，字典是可以序列化的。</p>
<p>本节课我们需要知道<code>JSONEncoder.default</code>函数的作用和意义，下节课再来覆写<code>JSONEncoder.default</code>方法。</p>
<h3 id="7-3-不完美的对象转字典"><a class="header-anchor" href="#7-3-不完美的对象转字典"></a>7-3 不完美的对象转字典</h3>
<h4 id="让-jsonify-调用自定义的-jsonencoder-default"><a class="header-anchor" href="#让-jsonify-调用自定义的-jsonencoder-default"></a>让 jsonify 调用自定义的 JSONEncoder.default</h4>
<p>首先我们肯定是不能修改第三方库包中的源码的，这是愚蠢的行为。我们应该在外部继承<code>JSONEncoder</code>，然后用自己定义的 <code>default</code>方法覆盖<code>JSONEncoder.default</code>方法。</p>
<p>我们现在 ginger/app/app.py 内继承<code>JSONEncoder</code>，再自定义 <code>default</code>打断点看看 <code>jsonify</code>的断点能不能进来？</p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051624.jpg" alt="image-20190104122451613"></p>
<p>因为模型里面的属性非常多，它不是一个简单的对象，我们可以先定义一个简单的对象，了解其作用原理之后再来序列化模型。</p>
<p>所以在 ginger/app/api/v1/user.py 中，自定义一个简单的类进行 <code>jsonify</code></p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051626.jpg" alt="image-20190104114731698"></p>
<p>我们打断点发现并没有进入自定义的 <code>default</code>方法内部，而是进入了原来的<code>JSONEncoder.default</code>内部。</p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051628.jpg" alt="image-20190104115308848"></p>
<p>显然这里并没有实现覆盖，我们只是定义了，但是 flask 并不知道要去调用我们自己实现的<code>default</code>函数，那怎么办呢？我们还需要用我们自己定义的 <code>JSONEncoder</code>来代替 flask 原来的<code>JSONEncoder</code>，所以我们还需要在 ginger/app/app.py 内做一些改写：</p>
<pre><code class="language-python">from flask import Flask as _Flask
from flask.json import JSONEncoder as _JSONEncoder


class JSONEncoder(_JSONEncoder):
    def default(self, o):
        pass


class Flask(_Flask):			# 用我们自定义的 Flask 核心对象继承原有的 Flask 核心对象
    json_encoder = JSONEncoder	# 用我们自定义的 JSONEncoder 替代 Flask 原有的 JSONEncoder
</code></pre>
<p>再次测试就会发现，<code>jsonify</code>断点会成功的进入到自定义的<code>JSONEncoder.default</code>函数中去。</p>
<h4 id="编写自定义的-jsonencoder-default"><a class="header-anchor" href="#编写自定义的-jsonencoder-default"></a>编写自定义的 JSONEncoder.default</h4>
<p>接下来我们需要编写一个具体的业务逻辑来实现自定义的 <code>default</code>函数。因为字典是可以被序列化的最合适的类型，所以我们如果能将对象转换成字典就可以了。</p>
<p>那么如何把一个对象转化成字典呢？</p>
<p>答：使用对象内置的方法 <code>o.__dict__</code></p>
<p>但是测试结果返回的确实<code>{}</code>空字典。这显然不是我们想要的，我们希望在这里能够显示出我们所定义的对象的姓名和年龄属性（类变量）。但是这里没有实现，这是为什么呢？是我们的思路错了吗？其实我们的思路没有错，只是我们 Python 的细节还是不够注意。这里我们就要分析一下<code>o.__dict__</code>到底有没有值？再次调试发现这个 <code>__dict__</code>是一个空的字典，没有值。并不是我们想要的<code>age</code>和 <code>name</code>，怎么回事儿呢？<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051630.jpg" alt="image-20190104123320873"></p>
<blockquote>
<p>小知识：</p>
<pre><code class="language-python">class QiYue:
    name = 'qiyue'
    age = 18
</code></pre>
<p>这里定义的 name、age 是类变量，而不是实例变量，类变量是不会保存在<code>__dict__</code>中的，只有实例变量才会保存在<code>__dict__</code>中。</p>
</blockquote>
<p>我们可以做一个验证，修改 <code>QiYue</code>为：</p>
<pre><code class="language-python">class QiYue:
    name = 'qiyue'
    age = 18

    def __init__(self):
        self.gender = 'male'
</code></pre>
<p>然后在进行调试可以发现<code>__dict__</code>里面有值了，如图：</p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051632.jpg" alt="image-20190104124458036"></p>
<p>到这里对象的实例变量已经可以完成序列化了，那么问题来了，如果我想要把对象的类变量和实例变量都进行序列化，该怎么办呢？那么显然这种简化的<code>o.__dict__</code>的方式不行，我们需要另外的方式将所有的变量都转化为字典。下节课再解决这个问题。</p>
<h3 id="7-4-深入理解dict的机制"><a class="header-anchor" href="#7-4-深入理解dict的机制"></a>7-4 深入理解dict的机制</h3>
<p>基础问题：出了通过<code>o.__dict__</code>的方式把一个对象转换成字典之外还有别的方式能够实现这种转换吗？</p>
<p>因为使用这种方式我们只能拿到对象的实例变量，拿不到对象的类变量。那我们可能需要深入了解一下 <code>dict()</code>函数。大多数情况下，创建字典的方法有两种：</p>
<ol>
<li>
<p>第一种</p>
<pre><code class="language-python">r = {'nickname': 'qiyue', 'age': 18}
</code></pre>
</li>
<li>
<p>第二种</p>
<pre><code class="language-python">r = dict(nickname='qiyue', age=18)
</code></pre>
</li>
</ol>
<p><code>dict</code>函数的功能是非常强大的，它不仅可以创建字典，它还有很多种其他灵活的运用方式。<strong>如果在调用<code>dict</code>函数的时候传入了一个对象，那么 python 会调用该对象下面的一个特殊的方法<code>keys</code>。</strong></p>
<p>python 为什么回去调用 <code>keys</code>这个方法呢？</p>
<p>原因就在于我们的目的是生成一个字典，既然要生成字典的话就有两个最重要的因素：键、值。所以调用 <code>keys</code>的目的就是为了拿到这个字典里所有的键。至于说些键有哪些，那么完全由我们自己来定义，因为 <code>keys</code>方法完全由我们自己来实现。只要 <code>keys</code>方法返回的是<mark>一种序列类型</mark>就是可以的，那么我们可以这么写：</p>
<pre><code class="language-python"># 返回元组
def keys(self):
    return 'name', 'age', 'gender'

# 返回列表
def keys(self):
    return ['name', 'age', 'gender']
</code></pre>
<p>现在一个字典里所有的键我们确定了，那么每一个键里所对应的值该如何确定？</p>
<p>对象会使用<code>o['name']</code>、<code>o['age']</code>、<code>o['gender']</code>的方式来访问键的值，但是这种方式是字典的访问方式，而<code>o</code>是对象。那么问题来了对象可以用这种方式来访问呢？默认情况下是不可以的。但是如果我们为类增加一个方法<code>__getitem__</code>就可以使用中括号的方式访问类下面的相关变量了。当 python 遇到对象使用中括号方式的时候就会调用<code>__getitem__</code>方法，然后把<code>name</code>、<code>age</code>、<code>gender</code>等键的名字当做 <code>item</code>参数传入。</p>
<p>如果通过一个<code>object</code>下面属性的名字来拿到这个属性的值呢？</p>
<pre><code class="language-python">getattr(object, item)
</code></pre>
<p>完整的测试代码如下：</p>
<pre><code class="language-python">class QiYue:
    name = 'qiyue'
    age = 18

    def __init__(self):
        self.gender = 'male'

    @staticmethod
    def keys():
        return 'name', 'age', 'gender'

    def __getitem__(self, item):
        return getattr(self, item)


o = QiYue()
print(o['name'], o['age'], o['gender'])
print(o.keys())
print(dict(o))
-------------------------------------------------------------------------
执行结果：
qiyue 18 male
('name', 'age', 'gender')
{'name': 'qiyue', 'age': 18, 'gender': 'male'}
</code></pre>
<h3 id="7-5-一个元素的元组要特别注意"><a class="header-anchor" href="#7-5-一个元素的元组要特别注意"></a>7-5 一个元素的元组要特别注意</h3>
<p>上节测试代码中有一个地方需要注意下，就是 <code>keys</code>方法 <code>return</code>的如果是只有一个元素的元组的时候一定要加<mark>逗号</mark><code>,</code>，否则会报错</p>
<pre><code class="language-python">@staticmethod
def keys():
    return 'name',
</code></pre>
<p>如果返回的是列表类型就可以避免这种错误。</p>
<h3 id="7-6-序列化sqlalchemy模型"><a class="header-anchor" href="#7-6-序列化sqlalchemy模型"></a>7-6 序列化SQLAlchemy模型</h3>
<p>有了前几节的知识，序列化<code>user</code>模型就很简单了。首先将 <code>get_user</code>视图函数改为：</p>
<pre><code class="language-python">@api.route('/&lt;int:uid&gt;', methods=['GET'])
@auth.login_required
def get_user(uid):
    user = User.query.get_or_404(uid)
    return jsonify(user)
</code></pre>
<p>其次再修改 ginger/app/models/user.py</p>
<pre><code class="language-python">class User(Base):
    id = Column(Integer, primary_key=True)
    email = Column(String(24), unique=True, nullable=False)
    nickname = Column(String(24), unique=True)
    auth = Column(SmallInteger, default=1)
    _password = Column('password', String(128))

    def keys(self):
        return ['id', 'email', 'nickname', 'auth']

    def __getitem__(self, item):
        return getattr(self, item)
    
    # 下方代码不需要更改，此处省略
</code></pre>
<p>接着我们使用 postman 测试就行了，毫无疑问此处测试成功。</p>
<h3 id="7-7-完善序列化"><a class="header-anchor" href="#7-7-完善序列化"></a>7-7 完善序列化</h3>
<p>本节我们来做一些重构：</p>
<ol>
<li>
<p>基本上来说每一个模型类都要进行序列化，所以说每一个模型类里都要写 <code>keys</code>、<code>__getitem__</code>方法。这就比较烦了，那我们可以优化一下，把一些公共的方法提取到基类里面去。</p>
<ul>
<li><code>keys</code>方法比较的个性化，它必须根据不同的模型类来输出不同的属性名称，所以说<code>keys</code>不能提取;</li>
<li><code>__getitem__</code>是可以方法哦 <code>Base</code>基类里的。</li>
</ul>
</li>
<li>
<p>ginger/app/app.py 内我们自定义的<code>JSONEncoder.default</code>，写的太简陋了，我们只处理了具有 <code>keys</code>、<code>__getitem__</code>方法的对象，如果对象没有这两个方法就会报错，所以需要在自定义的<code>JSONEncoder.default</code>内部做判断，如果对象没有这两个方法的话就返回 <code>ServerError</code>表示服务器内部错误。</p>
<pre><code class="language-python">class JSONEncoder(_JSONEncoder):
    def default(self, o):
        if hasattr(self, 'keys') and hasattr(self, '__getitem__'):
            return dict(o)
        raise ServerError()
</code></pre>
</li>
<li>
<p>关于<code>JSONEncoder.default</code>方法，还有一个很重要的特性：<code>default</code>函数式递归调用的，只要遇到不能序列化的对象就会调用 <code>default</code>函数。并且把不能序列化的对象当做<code>o</code>传入<code>default</code>函数里，让我们来处理。</p>
<p>在我们之前的调试过程中之所以没有遇见<code>default</code>是因为我们定义的对象的属性都是一些简单的数据结构。如果遇见对象的属性是另一个对象的话，那么 <code>default</code>就会递归调用了。<br>
如下图所示，<code>User</code>模型内添加 <code>time</code>属性，<code>datetime</code>是 python 的 <code>date</code> 类型，<code>keys return</code> 的地方添加 <code>time</code>。</p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051633.jpg" alt="image-20190104173008519"><br>
调试的时候第一次调用 <code>default</code>的时候<code>o: User 1</code>：<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051634.jpg" alt="image-20190104173042175"><br>
调试的时候第二次调用 <code>default</code>的时候<code>o: 2019-01-01</code>，这就是<code>default</code>函数的递归调用。</p>
<p><img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051636.jpg" alt="image-20190104173102158"><br>
最后，如果大家以后遇到不能序列化的对象就在自定义的<code>JSONEncoder.default</code>里面添加 <code>if</code>语句来处理。</p>
</li>
<li>
<p>优化 ginger/app/app.py 文件<br>
<img src="https://klause-blog-pictures.oss-cn-shanghai.aliyuncs.com/2019-05-14-051638.jpg" alt="image-20190104174333578"><br>
上面 <code>JSONEncoder</code>、<code>Flask</code>两个类基本上来说是固定不变的，但是 <code>register_blueprints</code>、<code>register_plugins</code>、<code>create_app</code>可能经常需要改动。</p>
<ul>
<li>我们更倾向于将 <code>JSONEncoder</code>、<code>Flask</code>放在单独的模块文件中作为一个独立的文件，所以将这两个类就放在 <a href="http://app.py" target="_blank" rel="noopener">app.py</a> 中；</li>
<li>把另外三个经常需要改动的函数放到 ginger/app/_<em>init</em>_.py 中比较合适，移动之后需要修改一下依赖导入的问题</li>
</ul>
</li>
</ol>
<h3 id="7-8-viewmodel对于api有意义吗"><a class="header-anchor" href="#7-8-viewmodel对于api有意义吗"></a>7-8 ViewModel对于API有意义吗</h3>
<p>在flask 高级编程中，我们为每一个模型会建立多个 <code>ViewMode</code>，在做网站的时候需要 ViewMode，那么在做 API 的时候还需要 ViewMode 吗？或者说 ViewMode 对于 API 来说有没有意义？</p>
<p><code>ViewMode</code>是为视图层提供个性化的视图模型的。这个视图模型和 sqlalchemy 直接返回回来的视图模型有什么区别呢？</p>
<p>sqlalchemy 返回的模型是原始模型，所谓原始模型就是这个模型下面所有的字段的格式基本上和数据库中存储的数据格式是一摸一样的。</p>
<p>但是数据库里存储的数据格式是前段需要的数据格式吗？显然不一定是。</p>
<p>理论上来说所有的数据都可以在前端进行处理，但是后端不能把所有数据处理化的工作全部都丢给前端，有时候我们需要为前段考虑一下，为前段提供更加方便好用的接口。如果决定返回数据的格式是根据具体的业务来的。原始模型是根据数据库来生成的，它的格式是一定的，但是我们在视图层中或者说在 API 的返回中一定要根据业务具体的个性化参数格式。那这必然存在原始模型向视图模型转化的过程。这个过程就是在<code>ViewMode</code>中进行转化。</p>
<p>如果没有<code>ViewMode</code> 的话，必然会将原始数据转化成各种各样的数据格式，所以说这样就污染了整个视图函数层。而且我们把具体的转换业务逻辑写在视图函数里是不利于复用的。这只是简单的情况，再复杂一点的就是需要返回的数据是<strong>多模型</strong>数据的组合。合成数据的过程可能相当的复杂，写在视图函数里肯定不合适，但是我们可以定义一个<code>ViewMode</code>来处理，在是视图函数里面调用就可以了。</p>
<p>对于严格意义的 RESTful（完全资源化的API）来说的话，<code>ViewMode</code>意义不大。因为之前说过了，资源意义上的 RESTful 是不太考虑业务逻辑的。它不会去考虑前端最终需要的数据格式，反正我只返回一种格式，至于你需要什么格式，你自己看着办。</p>

<div id="gitalk-container"></div>
<script src="https://cdn.bootcss.com/blueimp-md5/2.12.0/js/md5.min.js"></script><link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css"><script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>

		<script>
		var gitalkConfig = {"clientID":"9eb5bc3ac1e1ff3ddac0","clientSecret":"4b7ae28042282281295075c2bf0c97ff1791cfeb","repo":"HexoBlogComments","owner":"Annihilater","admin":["Annihilater"],"distractionFreeMode":false};
	    gitalkConfig.id = md5(location.pathname);
		var gitalk = new Gitalk(gitalkConfig);
	    gitalk.render("gitalk-container");
	    </script>
            </div>
            <hr/>

            

    <div class="reprint" id="reprint-statement">
        
            <div class="reprint__author">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-user">
                        文章作者:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="https://www.klause.cn" rel="external nofollow noreferrer">湮灭星空</a>
                </span>
            </div>
            <div class="reprint__type">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-link">
                        文章链接:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="https://www.klause.cn/2019/04/12/python-flask-gou-jian-ke-kuo-zhan-de-restful-api/di-7-zhang-mo-xing-dui-xiang-de-xu-lie-hua/">https://www.klause.cn/2019/04/12/python-flask-gou-jian-ke-kuo-zhan-de-restful-api/di-7-zhang-mo-xing-dui-xiang-de-xu-lie-hua/</a>
                </span>
            </div>
            <div class="reprint__notice">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-copyright">
                        版权声明:
                    </i>
                </span>
                <span class="reprint-info">
                    本博客所有文章除特別声明外，均采用
                    <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="external nofollow noreferrer" target="_blank">CC BY 4.0</a>
                    许可协议。转载请注明来源
                    <a href="https://www.klause.cn" target="_blank">湮灭星空</a>
                    !
                </span>
            </div>
        
    </div>

    <script async defer>
      document.addEventListener("copy", function (e) {
        let toastHTML = '<span>复制成功，请遵循本文的转载规则</span><button class="btn-flat toast-action" onclick="navToReprintStatement()" style="font-size: smaller">查看</a>';
        M.toast({html: toastHTML})
      });

      function navToReprintStatement() {
        $("html, body").animate({scrollTop: $("#reprint-statement").offset().top - 80}, 800);
      }
    </script>



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            
                                <a href="/tags/python/">
                                    <span class="chip bg-color">python</span>
                                </a>
                            
                                <a href="/tags/flask/">
                                    <span class="chip bg-color">flask</span>
                                </a>
                            
                                <a href="/tags/restful/">
                                    <span class="chip bg-color">restful</span>
                                </a>
                            
                                <a href="/tags/api/">
                                    <span class="chip bg-color">api</span>
                                </a>
                            
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="/libs/share/css/share.min.css">

<div id="article-share">
    
    
    <div class="social-share" data-sites="twitter,facebook,google,qq,qzone,wechat,weibo,douban,linkedin" data-wechat-qrcode-helper="<p>微信扫一扫即可分享！</p>"></div>
    <script src="/libs/share/js/social-share.min.js"></script>
    

    

</div>

                </div>
            </div>
            
        </div>
    </div>

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/2019/04/12/python-flask-gou-jian-ke-kuo-zhan-de-restful-api/di-6-zhang-token-yu-httpbasic-yan-zheng-yong-ling-pai-lai-guan-li-yong-hu/">
                    <div class="card-image">
                        
                        
                        <img src="/medias/featureimages/19.jpg" class="responsive-img" alt="第6章 Token与HTTPBasic验证 —— 用令牌来管理用户">
                        
                        <span class="card-title">第6章 Token与HTTPBasic验证 —— 用令牌来管理用户</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            在我的TP5课程里，我们使用令牌的方式是服务器缓存的方式。那么在Python Flask中我们换一种令牌的发放方式。我们将用户的信息加密后作为令牌返回到客户端，客户端在访问服务器API时必须以HTTP Basic的方式携带令牌，我们再读取令
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <i class="far fa-clock fa-fw icon-date"></i>2019-04-12
                        </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/ginger/" class="post-category">
                                    ginger
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/python/">
                        <span class="chip bg-color">python</span>
                    </a>
                    
                    <a href="/tags/flask/">
                        <span class="chip bg-color">flask</span>
                    </a>
                    
                    <a href="/tags/restful/">
                        <span class="chip bg-color">restful</span>
                    </a>
                    
                    <a href="/tags/api/">
                        <span class="chip bg-color">api</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/2019/04/12/python-flask-gou-jian-ke-kuo-zhan-de-restful-api/di-9-zhang-shi-xian-bu-fen-yu-shu-xiao-cheng-xu-gong-neng/">
                    <div class="card-image">
                        
                        
                        <img src="/medias/featureimages/14.jpg" class="responsive-img" alt="第9章 实现部分鱼书小程序功能">
                        
                        <span class="card-title">第9章 实现部分鱼书小程序功能</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            理论必须结合实践，我们提供一个简单的鱼书小程序，编写他的业务接口，并用小程序来进行API的检验
9-1 小程序演示API调用效果
9-2 模糊搜索书籍


新建 search视图函数，如何来设计 search视图函数呢？既然是搜索图书，必定
                        
                    </div>
                    <div class="publish-info">
                            <span class="publish-date">
                                <i class="far fa-clock fa-fw icon-date"></i>2019-04-12
                            </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/ginger/" class="post-category">
                                    ginger
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/python/">
                        <span class="chip bg-color">python</span>
                    </a>
                    
                    <a href="/tags/flask/">
                        <span class="chip bg-color">flask</span>
                    </a>
                    
                    <a href="/tags/restful/">
                        <span class="chip bg-color">restful</span>
                    </a>
                    
                    <a href="/tags/api/">
                        <span class="chip bg-color">api</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>



<!-- 代码块功能依赖 -->
<script type="text/javascript" src="/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="/libs/codeBlock/codeLang.js"></script>

    
<!-- 代码块复制 -->

<script type="text/javascript" src="/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="/libs/codeBlock/codeShrink.js"></script>


<!-- 代码块折行 -->

<style type="text/css">
code[class*="language-"], pre[class*="language-"] { white-space: pre !important; }
</style>

    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            // headingsOffset: -205,
            headingSelector: 'h2, h3, h4, h5, h6'
        });

        // modify the toc link href to support Chinese.
        let i = 0;
        let tocHeading = 'toc-heading-';
        $('#toc-content a').each(function () {
            $(this).attr('href', '#' + tocHeading + (++i));
        });

        // modify the heading title id to support Chinese.
        i = 0;
        $('#articleContent').children('h2, h3, h4, h5, h6').each(function () {
            $(this).attr('id', tocHeading + (++i));
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>



    <footer class="page-footer bg-color">
    <div class="container row center-align">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            <span id="year">年份</span>
            <a href="https://www.klause.cn" target="_blank">湮灭星空</a>
            |&nbsp;Powered by&nbsp;<a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;<a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a>
            <br>
            
            &nbsp;<i class="fas fa-chart-area"></i>&nbsp;站点总字数:&nbsp;<span
                class="white-color">199.2k</span>&nbsp;字
            
            
            
            
            
            
            <span id="busuanzi_container_site_pv">
                |&nbsp;<i class="far fa-eye"></i>&nbsp;总访问量:&nbsp;<span id="busuanzi_value_site_pv"
                    class="white-color"></span>&nbsp;次
            </span>
            
            
            <span id="busuanzi_container_site_uv">
                |&nbsp;<i class="fas fa-users"></i>&nbsp;总访问人数:&nbsp;<span id="busuanzi_value_site_uv"
                    class="white-color"></span>&nbsp;人
            </span>
            
            <br>
            
            <span id="sitetime">载入运行时间...</span>
            <script>
                function siteTime() {
                    window.setTimeout("siteTime()", 1000);
                    var seconds = 1000;
                    var minutes = seconds * 60;
                    var hours = minutes * 60;
                    var days = hours * 24;
                    var years = days * 365;
                    var today = new Date();
                    var startYear = "2019";
                    var startMonth = "6";
                    var startDate = "28";
                    var startHour = "0";
                    var startMinute = "0";
                    var startSecond = "0";
                    var todayYear = today.getFullYear();
                    var todayMonth = today.getMonth() + 1;
                    var todayDate = today.getDate();
                    var todayHour = today.getHours();
                    var todayMinute = today.getMinutes();
                    var todaySecond = today.getSeconds();
                    var t1 = Date.UTC(startYear, startMonth, startDate, startHour, startMinute, startSecond);
                    var t2 = Date.UTC(todayYear, todayMonth, todayDate, todayHour, todayMinute, todaySecond);
                    var diff = t2 - t1;
                    var diffYears = Math.floor(diff / years);
                    var diffDays = Math.floor((diff / days) - diffYears * 365);
                    var diffHours = Math.floor((diff - (diffYears * 365 + diffDays) * days) / hours);
                    var diffMinutes = Math.floor((diff - (diffYears * 365 + diffDays) * days - diffHours * hours) /
                        minutes);
                    var diffSeconds = Math.floor((diff - (diffYears * 365 + diffDays) * days - diffHours * hours -
                        diffMinutes * minutes) / seconds);
                    if (startYear == todayYear) {
                        document.getElementById("year").innerHTML = todayYear;
                        document.getElementById("sitetime").innerHTML = "本站已安全运行 " + diffDays + " 天 " + diffHours +
                            " 小时 " + diffMinutes + " 分钟 " + diffSeconds + " 秒";
                    } else {
                        document.getElementById("year").innerHTML = startYear + " - " + todayYear;
                        document.getElementById("sitetime").innerHTML = "本站已安全运行 " + diffYears + " 年 " + diffDays +
                            " 天 " + diffHours + " 小时 " + diffMinutes + " 分钟 " + diffSeconds + " 秒";
                    }
                }
                setInterval(siteTime, 1000);
            </script>
            
            <br>
            
            <span id="icp"><img src="/medias/icp.png" style="vertical-align: text-bottom;" />
                <a href="http://www.beian.miit.gov.cn" target="_blank">皖ICP备18005729号-1</a>
            </span>
            
        </div>
        <div class="col s12 m4 l4 social-link social-statis">
    <a href="https://github.com/Annihilater" class="tooltipped" target="_blank" data-tooltip="访问我的GitHub" data-position="top" data-delay="50">
        <i class="fab fa-github"></i>
    </a>



    <a href="mailto:yanmiexingkong@gmail.com" class="tooltipped" target="_blank" data-tooltip="邮件联系我" data-position="top" data-delay="50">
        <i class="fas fa-envelope-open"></i>
    </a>





    <a href="https://twitter.com/" class="tooltipped" target="_blank" data-tooltip="关注我的Twitter: https://twitter.com/" data-position="top" data-delay="50">
        <i class="fab fa-twitter"></i>
    </a>







    <a href="https://www.zhihu.com/" class="tooltipped" target="_blank" data-tooltip="关注我的知乎: https://www.zhihu.com/" data-position="top" data-delay="50">
        <i class="fab fa-zhihu1">知</i>
    </a>



    <a href="/atom.xml" class="tooltipped" target="_blank" data-tooltip="RSS 订阅" data-position="top" data-delay="50">
        <i class="fas fa-rss"></i>
    </a>

</div>
    </div>
</footer>

<div class="progress-bar"></div>

    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script src="/js/search.js"></script>
<script type="text/javascript">
$(function () {
    searchFunc("/" + "search.xml", 'searchInput', 'searchResult');
});
</script>
    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>


    <script src="/libs/materialize/materialize.min.js"></script>
    <script src="/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="/libs/aos/aos.js"></script>
    <script src="/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="/js/matery.js"></script>

    <!-- Global site tag (gtag.js) - Google Analytics -->


    <!-- Baidu Analytics -->

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    <script src="/libs/others/clicklove.js" async="async"></script>
    
    
    <script async src="/libs/others/busuanzi.pure.mini.js"></script>
    

    

    

    

    

    
    <script type="text/javascript" src="/libs/background/ribbon-dynamic.js" async="async"></script>
    
    
    
    <script src="/libs/instantpage/instantpage.js" type="module"></script>
    

</body>

</html>
